template< RETURN_TYPE_TEMPLATE COMMA_SEPARATOR ARGUMENTS_TYPES_TEMPLATE >
class Delegate ARGUMENTS_SPECIALIZATION
{
	/*!Signature type definitioin*/
	typedef RETURN_TYPE (*FuncSignature) ( ARGUMENTS_TYPES );

public:
	/*============================ METHODS =====================================*/

	/* Default constructor*/
	Delegate( ) : m_p_fn_table( NullFunctionStub::GetTable( ) ), m_p_obj( NULL ), m_p_fn( )
	{
	}

	/* Copy constructor*/
	Delegate( const Delegate& ar_delegate ) : 
		m_p_fn_table( ar_delegate.m_p_fn_table )
	   ,m_p_obj( ar_delegate.m_p_obj )
	   ,m_p_fn( ar_delegate.m_p_fn )
	{
		if( m_p_fn_table )
			m_p_fn_table->CopyObj( m_p_obj, ar_delegate.m_p_obj );
	}

	/* Assignment operator*/
	Delegate& operator = ( const Delegate& ar_delegate )
	{
		if( this != &ar_delegate )
			Delegate( ar_delegate ).Swap( *this );
		return *this;
	}

	/* Destructor*/
	~Delegate( )
	{
		if( m_p_fn_table )
			m_p_fn_table->DeleteObj( m_p_obj );
	}

	/* Swap the contents of two delegates*/
	void Swap( Delegate& ar_delegate )
	{
		swap( m_p_obj, ar_delegate.m_p_obj );
		swap( m_p_fn, ar_delegate.m_p_fn );
		swap( m_p_fn_table, ar_delegate.m_p_fn_table );
	}

	/* Tells if the delegate has been assigned a function or not */
	bool_t IsEmpty( void ) const
	{
		return m_p_fn_table->IsEmpty( );
	}

	/* Empties the delegates and resets it */
	void Reset( void )
	{
		Delegate( ).Swap( *this );
	}

	/* Set the delegate to a free function */
	void Set( FuncSignature ap_func )
	{
		FreeFunctionStub::Init( m_p_obj, m_p_fn, ap_func );
		m_p_fn_table = FreeFunctionStub::GetTable( );
	}

	/* Set the delegate to member function */
	template< typename ObjType, typename ClassType >
	void Set( ObjType& ap_obj, RETURN_TYPE (ClassType::*ap_mem_fn)( ARGUMENTS_TYPES ) )
	{
		typedef RETURN_TYPE (ClassType::*MemberFuncType)( ARGUMENTS_TYPES );

		struct SelectStub
		{
			typedef ct_if< is_pointer< ObjType >::value, 
						   MemberFnStub< remove_pointer< ObjType >::type, MemberFuncType >, 
						   MemberFnCopyObjStub< remove_pointer< ObjType >::type, MemberFuncType > >::result type; 
		};

		SelectStub::type::Init( m_p_obj, m_p_fn, ap_obj, ap_mem_fn );
		m_p_fn_table = SelectStub::type::GetTable( );
	}

	/* Set the delegate to const member function */
	template< typename ObjType, typename ClassType >
	void Set( ObjType& ap_obj, RETURN_TYPE (ClassType::*ap_mem_fun)( ARGUMENTS_TYPES ) const )
	{
		typedef RETURN_TYPE (ClassType::*MemberFuncType)( ARGUMENTS_TYPES ) const;

		struct SelectStub
		{
			typedef ct_if< is_pointer< ObjType >::value, 
						   MemberFnStub< remove_pointer< ObjType >::type, MemberFuncType >, 
						   MemberFnCopyObjStub< remove_pointer< ObjType >::type, MemberFuncType > >::result type;
		};

		SelectStub::type::Init( m_p_obj, m_p_fn, ap_obj, ap_mem_fn );
		m_p_fn_table = SelectStub::type::GetTable( );
	}

	RETURN_TYPE Invoke( ARGUMENTS_TYPES_NAMES )
	{
		return m_p_fn_table->Invoke( m_p_obj, m_p_fn COMMA_SEPARATOR ARGUMENTS_NAMES );
	}

	RETURN_TYPE operator( ) ( ARGUMENTS_TYPES_NAMES )
	{
		return Invoke( ARGUMENTS_NAMES );
	}

	bool_t operator < ( const Delegate< RETURN_TYPE COMMA_SEPARATOR ARGUMENTS_TYPES >& ar_dlgt ) const
	{
		if( this->m_p_fn_table->GetType( ) < ar_dlgt.m_p_fn_table->GetType( ) )
			return true;

		if( this->m_p_fn_table->GetObj( m_p_obj ) < ar_dlgt.m_p_fn_table->GetObj( ar_dlgt.m_p_obj ) )
			return true;

		s32_t memcmp_fn_sz = this->m_p_fn_table->GetFnSize( ) < ar_dlgt.m_p_fn_table->GetFnSize( ) ?
			this->m_p_fn_table->GetFnSize( ) : ar_dlgt.m_p_fn_table->GetFnSize( );

		s32_t memcmp_result = memcmp( &(this->m_p_fn), &(ar_dlgt.m_p_fn), memcmp_fn_sz );

		return memcmp_result == 0 ?
			( this->m_p_fn_table->GetFnSize( ) - ar_dlgt.m_p_fn_table->GetFnSize( ) ) < 0 :
			memcmp_result < 0;
	}

	bool_t operator == ( const Delegate< RETURN_TYPE COMMA_SEPARATOR ARGUMENTS_TYPES >& ar_dlgt ) const
	{
		if( this->m_p_fn_table->GetType( ) != ar_dlgt.m_p_fn_table->GetType( ) )
			return false;

		if( this->m_p_fn_table->GetObj( m_p_obj ) != ar_dlgt.m_p_fn_table->GetObj( ar_dlgt.m_p_obj ) )
			return false;

		s32_t memcmp_fn_sz = this->m_p_fn_table->GetFnSize( ) < ar_dlgt.m_p_fn_table->GetFnSize( ) ?
			this->m_p_fn_table->GetFnSize( ) : ar_dlgt.m_p_fn_table->GetFnSize( );

		s32_t memcmp_result = memcmp( &(this->m_p_fn), &(ar_dlgt.m_p_fn), memcmp_fn_sz );

		return memcmp_result == 0 ?
			( this->m_p_fn_table->GetFnSize( ) - ar_dlgt->m_p_fn_table->GetFnSize( ) ) == 0 :
			memcmp_result == 0;
	}

	bool_t operator != ( const Delegate< RETURN_TYPE COMMA_SEPARATOR ARGUMENTS_TYPES >& ar_dlgt ) const
	{
		return ! ( (*this) == ar_dlgt );
	}

	bool_t operator > ( const Delegate< RETURN_TYPE COMMA_SEPARATOR ARGUMENTS_TYPES >& ar_dlgt ) const
	{
		return !( (*this) < ar_dlgt || (*this) == ar_dlgt );
	}

	bool_t operator <= ( const Delegate< RETURN_TYPE COMMA_SEPARATOR ARGUMENTS_TYPES >& ar_dlgt ) const
	{
		return (*this) < ar_dlgt || (*this) == ar_dlgt;
	}

	bool_t operator >= ( const Delegate< RETURN_TYPE COMMA_SEPARATOR ARGUMENTS_TYPES >& ar_dlgt ) const
	{
		return !( (*this) < ar_dlgt );
	}

private:
	/*========================= TYPE DEFINITIONS ================================*/
	/* Struct that holds the behavioural functions to manage the function and object pointers */
	struct FunctionTable
	{
		RETURN_TYPE		(*Invoke)( const void* ap_obj, any_function_pointer a_func COMMA_SEPARATOR ARGUMENTS_TYPES_NAMES );
		bool_t			(*IsEmpty)( void );
		void			(*CopyObj)( const void*& apr_this_obj, const void* ap_obj );
		void			(*DeleteObj)( const void* ap_obj );
		FunctionType	(*GetType)( void );
		const void*		(*GetObj)( const void* );
		u32_t			(*GetFnSize)( void );
	};

	/* Default stub with no behaviour */
	struct NullFunctionStub
	{
		static RETURN_TYPE Invoke( const void* ap_obj, any_function_pointer a_func COMMA_SEPARATOR ARGUMENTS_TYPES_NAMES )
		{
			/*ASSERT( false, "Bad function call: NULL Stub!!" );*/
			return (RETURN_TYPE)NULL;
		}

		static bool_t IsEmpty( void )
		{
			return true;
		}

		static void CopyObj( const void*& apr_this_obj, const void* ap_obj )
		{
			apr_this_obj = NULL;
		}

		static void DeleteObj( const void* apr_this_obj )
		{
			/*Do nothing*/
		}

		static FunctionType GetType( void )
		{
			return NULL_FUNC_TYPE;
		}

		static const void* GetObj( const void* ap_obj )
		{
			return NULL;
		}

		static u32_t GetFnSize( void )
		{
			return 0;
		}

		static const FunctionTable* GetTable( void )
		{
			static const FunctionTable sg_table = { &Invoke, &IsEmpty, &CopyObj, &DeleteObj, &GetType, &GetObj, &GetFnSize };
			return &sg_table;
		}
	};

	/* Stub with behaviour functions for a free function */
	struct FreeFunctionStub
	{
		static void Init( const void*& apr_this_obj, any_function_pointer& a_func, typename FuncSignature ap_func )
		{
			apr_this_obj = NULL;
			a_func.m_p_free_function = reinterpret_cast< generic_fn* >( ap_func );
		}

		static RETURN_TYPE Invoke( const void* ap_obj, any_function_pointer a_func COMMA_SEPARATOR ARGUMENTS_TYPES_NAMES )
		{
			return reinterpret_cast< RETURN_TYPE (*)( ARGUMENTS_TYPES ) >( a_func.m_p_free_function )( ARGUMENTS_NAMES );
		}

		static bool_t IsEmpty( void )
		{
			return false;
		}

		static void CopyObj( const void*& apr_this_obj, const void* ap_obj )
		{
			apr_this_obj = ap_obj;
		}

		static void DeleteObj( const void* ap_obj )
		{
			/*Do nothing*/
		}

		static FunctionType GetType( void )
		{
			return FREE_FUNC_TYPE;
		}

		static const void* GetObj( const void* ap_obj )
		{
			return NULL;
		}

		static u32_t GetFnSize( void )
		{
			return sizeof( FuncSignature );
		}

		static const FunctionTable* GetTable( void )
		{
			static const FunctionTable sg_table = { &Invoke, &IsEmpty, &CopyObj, &DeleteObj, &GetType, &GetObj, &GetFnSize };
			return &sg_table;
		}
	};

	/* Stub for a member function of an object that must be copied, e.g.: SmartPtr */
	template< typename ObjType, typename MemberFuncType >
	struct MemberFnCopyObjStub
	{
		static void Init( const void*& apr_this_obj, any_function_pointer& a_func, ObjType& ar_obj, MemberFuncType a_mem_func )
		{
			SAFE_DELETE( apr_this_obj );
			apr_this_obj = new ObjType( ar_obj );
			a_func.m_p_member_function = reinterpret_cast_hack< MemberFuncType >::Convert( a_mem_func );
		}

		static RETURN_TYPE Invoke( const void* ap_obj, any_function_pointer a_func COMMA_SEPARATOR ARGUMENTS_TYPES_NAMES )
		{
			ObjType* p_obj = static_cast< ObjType* >( const_cast< void* >( ap_obj ) );
			return ( ( (*p_obj).GetPtr( ) )->*reinterpret_cast_hack< MemberFuncType >::Restore( a_func.m_p_member_function ) )( ARGUMENT_NAMES );
		}

		static bool_t IsEmpty( void )
		{
			return false;
		}

		static void CopyObj( const void*& apr_this_obj, const void* ap_obj )
		{
			apr_this_obj = new ObjType( *static_cast< ObjType* >( ap_obj ) );
		}

		static void DeleteObj( const void* ap_obj )
		{
			delete static_cast< const ObjType* >( ap_obj );
			ap_obj = NULL;
		}

		static FunctionType GetType( void )
		{
			return MEMBER_METHOD_TYPE;
		}

		static const void* GetObj( const void* ap_obj )
		{
			ObjType* p_obj = static_cast< ObjType* >( ap_obj );
			return static_cast< const void* >( (*p_obj).GetPtr( ) );
		}

		static u32_t GetFnSize( void )
		{
			return sizeof( MemberFuncType );
		}

		static const FunctionTable* GetTable( void )
		{
			static const FunctionTable sg_table = { &Invoke, &IsEmpty, &CopyObj, &DeleteObj, &GetType, &GetObj, &GetFnSize };
			return &sg_table;
		}
	};

	/* Stub for a member function of an object that not needs to be copied */
	template< typename ObjType, typename MemberFuncType >
	struct MemberFnStub
	{
		static void Init( const void*& apr_this_obj, any_function_pointer& a_func, ObjType* ap_obj, MemberFuncType a_mem_func )
		{
			apr_this_obj = ap_obj;
			a_func.m_p_member_function = reinterpret_cast_hack< MemberFuncType >::Convert( a_mem_func );
		}

		static RETURN_TYPE Invoke( const void* ap_obj, any_function_pointer a_func COMMA_SEPARATOR ARGUMENTS_TYPES_NAMES )
		{
			ObjType* p_obj = static_cast< ObjType* >( const_cast< void* >( ap_obj ) );
			return ( p_obj->*reinterpret_cast_hack< MemberFuncType >::Restore( a_func.m_p_member_function ) )( ARGUMENTS_NAMES );
		}

		static bool_t IsEmpty( void )
		{
			return false;
		}

		static void CopyObj( const void*& apr_this_obj, const void* ap_obj )
		{
			apr_this_obj = ap_obj;
		}

		static void DeleteObj( const void* ap_obj )
		{
			ap_obj = NULL;
		}

		static FunctionType GetType( void )
		{
			return MEMBER_METHOD_TYPE;
		}

		static const void* GetObj( const void* ap_obj )
		{
			return ap_obj;
		}

		static u32_t GetFnSize( void )
		{
			return sizeof( MemberFuncType );
		}

		static const FunctionTable* GetTable( void )
		{
			static const FunctionTable sg_table = { &Invoke, &IsEmpty, &CopyObj, &DeleteObj, &GetType, &GetObj, &GetFnSize };
			return &sg_table;
		}
	};

	/*=========================== DATA MEMBERS ==================================*/

	/* Pointer to the object instance in a case of a member function */
	const void*					m_p_obj;
	/* Pointer to the function */
	any_function_pointer	m_p_fn;
	/* Table of functions that define the behaviour of the delegate for each type of function */
	const FunctionTable*	m_p_fn_table;

};